﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.Threading;
using System.Reflection;
using System.ComponentModel;

using E = System.Linq.Expressions.Expression;
using OE = ReactiveCode.Observables.Expressions.ObservableExpressionExtensions;

namespace ReactiveCode.Observables
{
    using Expressions;
    using Util;
    using Util.Expressions;
    using Observables;

    delegate E ConvertToObservable(E expression, Func<E, E> convertSubexpression);

    public static partial class ChangeListening
    {
        static LinkedList<ConvertToObservable> _converters = new LinkedList<ConvertToObservable>();

        static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        internal static E ConvertToObservable(Expression expression, Func<E, E> convertSubexpression)
        {
            Contract.Requires(expression != null);
            Contract.Requires(convertSubexpression != null);
            Contract.Ensures(Contract.Result<E>() == null || Contract.Result<E>().Type == typeof(IObservable<>).MakeGenericType(expression.Type));

            Type expectedType = typeof(IObservable<>).MakeGenericType(expression.Type);

            using(_lock.ReadLock())
            {
                for (var n = _converters.Last; n != null; n = n.Previous)
                {
                    var result = n.Value(expression, convertSubexpression);
                    if (result != null)
                    {
                        if (result.Type != expectedType)
                            throw new ConvertToObservableException(expectedType, result);
                        return result;
                    }
                }
            }

            return null;
        }
        public static IObservable<T> Observe<T>(Expression<Func<T>> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            var factory = new ObservableConverter().ConvertEval<IObservable<T>>(expression.Body);
            return factory(null);
        }
        public static IObservable<TValue> Observe<TSource, TValue>(this IObservable<TSource> source, Expression<Func<TSource, TValue>> expression)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            var converter = new ObservableConverter();
            var factory = (Expression<Func<TSource, IObservable<TValue>>>)converter.Convert(expression, E.Constant(null, typeof(IStateDecorator)));
            return source.SelectMany(factory.Compile());
        }

        #region RegisterConverter

        static IDisposable RegisterConverter(ConvertToObservable converter)
        {
            Contract.Requires(converter != null);
            using (_lock.WriteLock())
            {
                var node = _converters.AddLast(converter);
                return Disposable.Create(delegate
                {
                    using (_lock.WriteLock())
                    {
                        node.List.Remove(node);
                    }
                });
            }
        }

        static IDisposable RegisterMemberInfoConverter<TMember, TConverter>(Func<TMember, TConverter> converterSelector, Func<TConverter, TMember, Func<E[], E>> finalConvert)
            where TMember: MemberInfo
        {
            Contract.Requires(converterSelector != null && finalConvert != null);
            return RegisterConverter((expr, convertSubexpression) =>
            {
                MemberInfo member;
                var args = expr.ExtractMemberArgs(out member);
                var typedMember = member as TMember;
                if (args == null || typedMember == null || typedMember.DeclaringType == null)
                    return null;

                var converter = converterSelector(typedMember);
                if (converter == null)
                    return null;
                var converter2 = finalConvert(converter, typedMember);
                if (converter2 == null)
                    return null;

                args = args.ToList();
                return args.Any()
                    ? args.Select(convertSubexpression).CombineLatest(converter2, true)
                    : converter2(CollectionUtil.EmptyArray<E>());
            });
        }

        internal static IDisposable RegisterPropertyExpressionConverter(Func<PropertyInfo, Func<E, E>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterMemberInfoConverter(
                (PropertyInfo p) => !p.IsIndexer() ? converterSelector(p) : null,
                (converter, p) => args =>
                {
                    Contract.Assume(args != null && args.Length == 1);
                    return converter(args[0]);
                }
            );
        }
        internal static IDisposable RegisterIndexerExpressionConverter(Func<PropertyInfo, Func<E, E[], E>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterMemberInfoConverter(
                (PropertyInfo p) => p.IsIndexer() ? converterSelector(p) : null,
                (converter, p) => args =>
                {
                    E instance = ExtractInstance(p, ref args);
                    return converter(instance, args);
                }
            );
        }
        internal static IDisposable RegisterFieldExpressionConverter(Func<FieldInfo, Func<E, E>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterMemberInfoConverter(
                converterSelector,
                (converter, f) => args =>
                {
                    Contract.Assume(args != null && args.Length == 1);
                    return converter(args[0]);
                }
            );
        }
        internal static IDisposable RegisterMethodExpressionConverter(Func<MethodInfo, Func<E, E[], E>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterMemberInfoConverter(
                converterSelector,
                (converter, m) => args =>
                {
                    E instance = ExtractInstance(m, ref args);
                    return converter(instance, args);
                }
            );
        }

        static Func<TMember, Func<E, E>> ForExpression<TMember>(Func<TMember, Func<object, IObservable<object>>> selector)
            where TMember: MemberInfo
        {
            return m =>
            {
                var converter = selector(m);
                if (converter == null)
                    return null;
                Type valueType = ReflectionUtil.GetValueType(m);
                return x => E.Invoke(E.Constant(converter), x).Cast(valueType);
            };
        }
        static Func<TMember, Func<E, E[], E>> ForExpression<TMember>(Func<TMember, Func<object, object[], IObservable<object>>> selector)
            where TMember: MemberInfo
        {
            return m =>
            {
                var converter = selector(m);
                if (converter == null)
                    return null;
                Type valueType = ReflectionUtil.GetValueType(m);
                return (x, args) => E.Invoke(E.Constant(converter), x, E.NewArrayInit(typeof(object), args))
                    .Cast(valueType);
            };
        }
        public static IDisposable RegisterPropertyConverter(Func<PropertyInfo, Func<object, IObservable<object>>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterPropertyExpressionConverter(ForExpression(converterSelector));
        }
        public static IDisposable RegisterIndexerConverter(Func<PropertyInfo, Func<object, object[], IObservable<object>>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterIndexerExpressionConverter(ForExpression(converterSelector));
        }
        public static IDisposable RegisterFieldConverter(Func<FieldInfo, Func<object, IObservable<object>>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterFieldExpressionConverter(ForExpression(converterSelector));
        }
        public static IDisposable RegisterMethodConverter(Func<MethodInfo, Func<object, object[], IObservable<object>>> converterSelector)
        {
            Contract.Requires<ArgumentNullException>(converterSelector != null, "converterSelector");
            return RegisterMethodExpressionConverter(ForExpression(converterSelector));
        }

        #endregion

        static E ExtractInstance(MemberInfo member, ref E[] args)
        {
            Contract.Requires(member != null);
            Contract.Requires(args != null);
            Contract.Requires(member.IsStatic() || (args != null && args.Length >= 1));

            if (member.IsStatic())
                return null;

            E instance = args[0];
            args = args.Skip(1).ToArray();
            return instance;
        }
    }
}